/*
 * Copyright 2013 JROSE
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *     http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

package org.jrose.core.jdbc.persistence.support;

import lombok.AccessLevel;
import lombok.NoArgsConstructor;
import org.apache.commons.lang3.ArrayUtils;
import org.apache.commons.lang3.StringUtils;
import org.jrose.api.annotation.cache.CacheKeyCandidate;
import org.jrose.api.annotation.cache.CacheKeyCandidates;
import org.jrose.api.annotation.persistence.Column;
import org.jrose.core.cache.generator.CacheKeyGenerator;
import org.jrose.core.cache.generator.CacheTypeGenerator;
import org.jrose.core.cache.generator.ObjectCacheKeyGenerator;
import org.jrose.core.jdbc.entity.field.PersistenceField;
import org.jrose.core.jdbc.entity.field.internal.ColumnField;
import org.jrose.core.jdbc.entity.field.internal.PrimaryKeyField;
import org.jrose.core.util.AnnotationUtils;
import org.springframework.util.Assert;

import java.io.Serializable;
import java.util.LinkedList;
import java.util.List;

@NoArgsConstructor(access = AccessLevel.PROTECTED)
abstract public class IntelligentCacheKeyGeneratingPersistence<E extends Serializable> extends CacheablePersistence<E> {
    @Override
    protected void generateCacheKeys(E source, List<String> keys) {
        if (!isCacheable()) {
            return;
        }

        Class<?> cacheType = CacheTypeGenerator.generate(this);
        if (cacheType == null) {
            return;
        }

        // automatically add primary key as cache key if necessary
        if (entityDefinition.hasPrimaryKey()) {
            List<Object> primaryKeyValues = new LinkedList<>();
            for (PrimaryKeyField primaryKeyField : entityDefinition.getPrimaryKeyFields()) {
                primaryKeyValues.add(primaryKeyField.get(source));
            }
            Object[] values = primaryKeyValues.toArray(new Object[primaryKeyValues.size()]);
            keys.add(CacheKeyGenerator.generateCacheKey(cacheType, values));

            // for those entities which has unique primary key
            if (values.length == 1) {
                keys.add(CacheKeyGenerator.generateCacheKey(cacheType, values[0]));
            }
        }

        CacheKeyCandidates candidates = AnnotationUtils.getAnnotation(getClass(), CacheKeyCandidates.class);
        if (candidates == null) {
            return;
        }
        // handle with fixed keys
        if (ArrayUtils.isNotEmpty(candidates.fixedKeys())) {
            for (String fixedKey : candidates.fixedKeys()) {
                keys.add(CacheKeyGenerator.generateCacheKey(cacheType, fixedKey));
            }
        }
        // handle with cache key candidates
        if (ArrayUtils.isNotEmpty(candidates.value())) {
            for (CacheKeyCandidate candidate : candidates.value()) {
                if (ArrayUtils.isEmpty(candidate.value())) {
                    continue;
                }
                List<String> names = new LinkedList<>();
                List<String> values = new LinkedList<>();
                for (CacheKeyCandidate.EACH each : candidate.value()) {
                    names.add(StringUtils.defaultString(each.name()));
                    String fieldName = each.field();
                    PersistenceField field = entityDefinition.getPersistenceField(fieldName);
                    Assert.notNull(field);
                    Object value = field.get(source);
                    if (value == null && field.getClass() == ColumnField.class) {
                        ColumnField columnField = (ColumnField) field;
                        Column.DefaultValue defaultValue = columnField.getAnnotation().defaultValue();
                        if (defaultValue != null && defaultValue.enable()) {
                            value = conversionService.convert(defaultValue.value(), field.getFieldType());
                        }
                    }
                    values.add(ObjectCacheKeyGenerator.generate(value));
                }
                String key = CacheKeyGenerator.generateCacheKey(cacheType,
                        names.toArray(new String[names.size()]),
                        values.toArray(new String[values.size()]));
                keys.add(key);
            }
        }
    }
}
